/* Copyright (C) 2008, 2009, 2010 Faindu GmbH, Switzerland.
* All Rights Reserved. The following is Source Code and is subject to all restrictions 
* on such code as contained in the license accompanying this project. */
package blocstac.editimage
{
	import blocstac.api.blocbox.ImageLoader;
	import blocstac.api.blocbox.MimeType;
	import blocstac.editimage.base.EditImageDataImpl;
	import blocstac.editimage.base.ImageLoaderImpl;
	import blocstac.editimage.base.ImageOperation;
	import blocstac.editimage.base.SizeProperties;
	import blocstac.editimage.base.StateManager;
	import blocstac.editimage.base.StateManagerImpl;
	import blocstac.editimage.base.ToolsComponent;
	import blocstac.editimage.base.ViewModel;
	import blocstac.editimage.crop.CropTool;
	import blocstac.editimage.data.AddImageOperation;
	import blocstac.editimage.rotate.RotateComponent;
	import blocstac.editimage.rotate.RotateTool;
	import blocstac.editimage.scale.ScaleTool;
	import blocstac.editimage.zoom.PanTool;
	import blocstac.editimage.zoom.ZoomComponent;
	import blocstac.editimage.zoom.ZoomTool;
	import blocstac.util.CreateLogger;
	import blocstac.util.Print;
	
	import flash.display.Bitmap;
	import flash.display.Graphics;
	import flash.events.ContextMenuEvent;
	import flash.events.MouseEvent;
	import flash.geom.Point;
	import flash.ui.ContextMenu;
	import flash.ui.ContextMenuItem;
	
	import mx.controls.Button;
	import mx.logging.ILogger;
	
	import spark.components.Group;
	import spark.core.SpriteVisualElement;
	
	
/**
 *  Dispatched when the user wants to see the image in a separate browser tab.
 *
 *  @eventType blocstac.editimage.ViewImageEvent.EVENT_VIEW_IMAGE
 */
[Event(name="eventViewImage", type="blocstac.editimage.ViewImageEvent")]
	
/**
 *  Dispatched when the user wants to download the image.
 *
 *  @eventType blocstac.editimage.SaveImageAsEvent.EVENT_SAVE_IMAGE_AS
 */
[Event(name="eventSaveImageAs", type="blocstac.editimage.SaveImageAsEvent")]

/**
 *  Dispatched when EditImage.canUndo() or EditImage.canRedo() changed.
 *
 *  @eventType blocstac.editimage.CanUndoRedoChangedEvent.EVENT_CAN_REDO_UNDO_CHANGED
 */
[Event(name="eventCanRedoUndoChanged", type="blocstac.editimage.CanUndoRedoChangedEvent")]

/**
 * 
 */
public class EditImage extends Group
{
	private static const LOG:ILogger = CreateLogger.withClassNameOf(EditImage);
	
	[Embed(source='/blocstac/editimage/assets/apply.png')]
	private var applyIconClass:Class;
	
	[Embed(source='/blocstac/editimage/assets/cancel.png')]
	private var cancelIconClass:Class;	
	
	private var _stateManager:StateManager = new StateManagerImpl(invalidateSize, invalidateDisplayList, canUndoRedoChanged)
	private var _sizeProperties:SizeProperties = new SizeProperties(_stateManager.viewModel)
	
	private var _rotateComponent:RotateComponent
	private var _imageComponent:Bitmap
	private var _toolsComponent:ToolsComponent
	private var _cropComponent:SpriteVisualElement
	
	private var _rotateTool:RotateTool
	private var _scaleTool:ScaleTool
	private var _cropTool:CropTool
	private var _zoomTool:ZoomTool
	private var _panTool:PanTool
	
	
	private var _applyCropButton:Button
	private var _cancelCropButton:Button
	private var _zoomComponent:ZoomComponent	
	private var _componentsInitialized:Boolean
	
	//--------------------------------------------------------------------------
	//
	//  Public methods
	//
	//--------------------------------------------------------------------------	
	//----------------------------------
	//  editState
	//----------------------------------
	private var _editState:EditState = EditState.zoomable
	private var _editStateChanged:Boolean = true
	/**
	 * Setting to false also clears the undo- and redo-history.
	 */
	public function get editState():EditState {
		return _editState
	}
	public function set editState(value:EditState):void {
		if (_editState != value) {
			_editState = value
			_editStateChanged = true
			invalidateProperties()
		}
	}
	
	//----------------------------------
	//  show
	//----------------------------------
	private var _imageLoader:ImageLoaderImpl
	private var _editImageData:EditImageDataImpl
	private var _showChanged:Boolean
	/**
	 * Either set a url to an image or initialize the ImageEdit with show().
	 * @see #url
	 */
	public function show(imageLoader:ImageLoader, config:String=null, mimeType:MimeType=null):void {
		if (_componentsInitialized) {
			throw Error("Image has already loaded.")
		}
		
		_editImageData = new EditImageDataImpl()
		_editImageData.config = config
		_editImageData.mimeType = mimeType
				
		_showChanged = true
		_imageLoader = ImageLoaderImpl(imageLoader)
		invalidateProperties()
	}

	//----------------------------------
	//  url
	//----------------------------------
	/**
	 * Either set a url to an image or initialize the ImageEdit with show().
	 * @see show()
	 */
	public function set url(value:String):void {
		if (_componentsInitialized) {
			throw Error("Image has already loaded.")
		}
		var imageLoader:ImageLoader = new ImageLoaderImpl(null)
		imageLoader.fromUrl(value)
		show(imageLoader, null)
	}

	//----------------------------------
	//  export
	//----------------------------------	
	/**
	 * Export all data of the image. This also contains information about e.g. the angle
	 * of the image.
	 */
	public function export():EditImageData {
		return _stateManager.export(_imageLoader)
	}

	//----------------------------------
	//  imageHasChanged
	//----------------------------------	
	/**
	 * Return true if the image has been modified by the user. 
	 * Zooming the image has no effect on <code>imageHasChanged</code>.
	 */
	public function imageHasChanged():Boolean {
		return _stateManager.imageHasChanged()
	}
	
	//----------------------------------
	//  Size, positions
	//----------------------------------
	/**
	 * Return true if the image is wider than the component. This results in an automatic scaled image.
	 * Return false if the width of the image is smaller than the component width.
	 */
	public function get imageIsWiderThanComponent():Boolean {
		return _sizeProperties.imageIsWiderThanComponent
	}
	
	/**
	 * 
	 */
	public function isOnImage(componentSpace:Point):Boolean {
		return _sizeProperties.isOnImage(componentSpace)
	}
	
	/**
	 * Transform <code>componentSpace</code> into the same space as the image. For example, if the image is scaled, the point 
	 * is moved by the scale.
	 */
	public function component2imageSpace(componentSpace:Point):Point {
		return _sizeProperties.component2imageSpace(componentSpace)
	}

	/**
	 * 
	 */
	public function image2componentSpace(imageSpace:Point):Point {
		return _sizeProperties.image2componentSpace(imageSpace)
	}
	
	//----------------------------------
	//  Undo, Redo, canUndo, canRedo
	//----------------------------------	
	/**
	 * Undo the last operation. If no operation can be undone, nothing happens.
	 * 
	 * @return true if an undo operation has been successfully applied. 
	 * @see canUndo() 
	 */
	public function undo():Boolean {
		return _stateManager.undo()
	}

	/**
	 * Redo the last undone operation. If no operation can be redone, nothing happens.
	 * 
	 * @return true if a redo operation has been successfully applied.
	 * @see canRedo() 
	 */
	public function redo():Boolean {
		return _stateManager.redo()
	}
	
	/** 
	 * Returns true if an undo operation can be applied. 
	 */
	public function canUndo():Boolean {
		return _stateManager.canUndo()
	}
	
	/** 
	 * Returns true if a redo operation can be applied. 
	 */
	public function canRedo():Boolean {
		return _stateManager.canRedo()
	}
	
	private function canUndoRedoChanged():void {
		dispatchEvent(new CanUndoRedoChangedEvent())
	}
	
	//--------------------------------------------------------------------------
	//
	//  Framework methods
	//
	//--------------------------------------------------------------------------	
	override protected function commitProperties():void {
		if (_showChanged) {
			_showChanged = false
			
			if (_imageLoader.hasLoaded()) {
				addImageToDisplay()
			} else {
				_imageLoader.whenCompleted(addImageToDisplay)
			}
			var mimeType:MimeType = MimeType.createFromFileName(_imageLoader.url)
			_stateManager.importData(_editImageData, mimeType)
		}
		
		if (_editStateChanged && _componentsInitialized) {
			_editStateChanged = false
			if (_editState.isEditable) {
				initControls()
			} 
			
			_stateManager.switchToEditState(_editState)
			_cropTool.updateEditState()
			_panTool.updateEditState()
			_zoomComponent.updateEditState()
			_toolsComponent.updateEditState()
			invalidateDisplayList()
		}
		super.commitProperties()
	}
	
	override protected function measure():void {
 		var viewModel:ViewModel = _stateManager.viewModel
		
		var componentWidth:Number = viewModel.componentWidth
		if (width != 0 && width < componentWidth) {
			measuredWidth = width
		} else {
			measuredWidth  = componentWidth
		}
		measuredHeight = viewModel.componentHeight
	}
		
	override protected function updateDisplayList(unscaledWidth:Number, unscaledHeight:Number):void {
		var viewModel:ViewModel = _stateManager.viewModel
 		if (_componentsInitialized) {
			if (viewModel.imageDataChanged) {
				viewModel.resetImageDataChanged()
				_imageComponent.bitmapData = viewModel.imageData
			}
			
			var componentWidth:Number = viewModel.componentWidth
			var fixScale:Number = _stateManager.maxComponentWidthInContainer(unscaledWidth)
			_zoomComponent.setMinimumAndValueTo(fixScale)
			_rotateComponent.updateUserViewScale()
			invalidateSize()
			/*trace("EditImage.updateDisplayList()", "unscaledWidth: "+unscaledWidth, 
				"componentWidth (old): "+Print.trunc(componentWidth), "componentWidth (new): "+Print.trunc(viewModel.componentWidth))*/
			
			_rotateComponent.transform.matrix = viewModel.rotationMatrix
			_imageComponent.transform.matrix = viewModel.imageMatrix
			_rotateComponent.updateUserViewScale()
			
			if (_editState.isEditable) {
				_rotateTool.update()
				_scaleTool.update()
				_cropTool.update()
			}
		} 
		
 		if (_zoomComponent) {
			_zoomComponent.move(5, unscaledHeight-16)
		}
		
		if (_applyCropButton && _applyCropButton.visible) {
			var buttonPosition:Point = _cropTool.buttonPosition()
			_applyCropButton.move(buttonPosition.x, buttonPosition.y)
			_cancelCropButton.move(buttonPosition.x + _cancelCropButton.width, buttonPosition.y)
		}
			
		super.updateDisplayList(unscaledWidth, unscaledHeight)
	}
		
	//--------------------------------------------------------------------------
	//
	//  Private methods
	//
	//--------------------------------------------------------------------------		
	private function addImageToDisplay():void {
		_imageComponent = _imageLoader.image
		
		var viewImageItem:ContextMenuItem = new ContextMenuItem(resourceManager.getString("editimage", "view_image_menu"))
		viewImageItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, viewImageHandler)
		var saveImageAsItem:ContextMenuItem = new ContextMenuItem(resourceManager.getString("editimage", "save_image_as_menu"))
		saveImageAsItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, saveImageAsHandler)
		
		var imageMenu:ContextMenu = new ContextMenu()
		imageMenu.hideBuiltInItems()
		imageMenu.customItems.push(viewImageItem)
		//imageMenu.customItems.push(saveImageAsItem)
			
		_rotateComponent = new RotateComponent(_stateManager, _imageComponent)
		_rotateComponent.contextMenu = imageMenu
		_toolsComponent = new ToolsComponent(_stateManager)
		_cropComponent = new SpriteVisualElement()
		_rotateComponent.addChild(_imageComponent)
		_rotateComponent.addChild(_toolsComponent)
		addElement(_rotateComponent)
		addElement(_cropComponent)
		
		var op:ImageOperation = new AddImageOperation(_imageComponent.bitmapData)
		_stateManager.apply(op)
		
		_componentsInitialized = true
		initControls()
		
		_zoomComponent = new ZoomComponent(_stateManager, this)
		addElement(_zoomComponent)
		
		_zoomTool = new ZoomTool(_stateManager, _zoomComponent)
		_zoomTool.init(_toolsComponent)
		
		_panTool = new PanTool(_stateManager, _rotateComponent)
		
		invalidateProperties()
	}
	
	private function viewImageHandler(event:ContextMenuEvent):void {
		dispatchEvent(new ViewImageEvent())
	}
	
	private function saveImageAsHandler(event:ContextMenuEvent):void {
		dispatchEvent(new SaveImageAsEvent())
	}
	
	private function initControls():void {
		if (_editState && _componentsInitialized) {
			if (! _rotateTool) {
				_rotateTool = new RotateTool(_stateManager, _rotateComponent)
				_rotateTool.init(_toolsComponent)
				
				_scaleTool = new ScaleTool(_stateManager, _imageComponent)
				_scaleTool.init(_toolsComponent)
				
				_cropTool = new CropTool(_stateManager, _cropComponent, _rotateComponent, _imageComponent, addCropButtons, hideCropButtons, invalidateDisplayList)
				_cropTool.init(_toolsComponent)
			}
		}
	}
	
	//----------------------------------
	//  Crop buttons
	//----------------------------------
	private function addCropButtons(applyButtonClickHandler:Function, cancelButtonClickHandler:Function):void {
		if (! _applyCropButton) {
			_applyCropButton = new Button()
			_applyCropButton.width = 26
			_applyCropButton.setStyle("icon", applyIconClass)
			_applyCropButton.toolTip = resourceManager.getString("editimage", 'applyCropButton_tooltip')
			_applyCropButton.addEventListener(MouseEvent.CLICK, applyButtonClickHandler, false, 0, true)
			addElement(_applyCropButton)
			
			_cancelCropButton = new Button()
			_cancelCropButton.width = 26
			_cancelCropButton.setStyle("icon", cancelIconClass)
			_cancelCropButton.toolTip = resourceManager.getString("editimage", "cancelCropButton_tooltip")
			_cancelCropButton.addEventListener(MouseEvent.CLICK, cancelButtonClickHandler, false, 0, true)
			addElement(_cancelCropButton)
		}
		_applyCropButton.visible = true
		_cancelCropButton.visible = true
		
		invalidateDisplayList()
	}
	
	private function hideCropButtons():void {
		if (_applyCropButton) {
			_applyCropButton.visible = false
			_cancelCropButton.visible = false
		}	
	}

}
}